/******************************************************************************* * Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package de.gebit.integrity.ui.utils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.Javadoc; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.TagElement; import org.eclipse.jdt.core.dom.TextElement; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.xtext.common.types.JvmField; import org.eclipse.xtext.common.types.JvmOperation; import org.eclipse.xtext.common.types.util.jdt.IJavaElementFinder; /** * This utility class contains various helper functions to aid in the exploration of Javadoc data attached to classes. * Only for use inside Eclipse! * * @author Rene Schneider - initial API and implementation * */ public final class JavadocUtil { private JavadocUtil() { // nothing to do } /** * The options to use when parsing a Java Abstract Syntax Tree. */ @SuppressWarnings("rawtypes") private static final Map ASTPARSER_OPTIONS = JavaCore.getDefaultOptions(); private static MethodDeclaration getMethodDeclaration(JvmOperation aMethod, IJavaElementFinder anElementFinder) { IJavaElement tempSourceMethod = (IJavaElement) anElementFinder.findElementFor(aMethod); // That parent may also be an instance of org.eclipse.jdt.internal.core.ClassFile - in that case there's // no Javadoc anyway if (tempSourceMethod.getParent().getParent() instanceof ICompilationUnit) { ICompilationUnit tempCompilationUnit = (ICompilationUnit) tempSourceMethod.getParent().getParent(); AbstractTypeDeclaration tempType = parseCompilationUnit(tempCompilationUnit); if (tempType instanceof TypeDeclaration) { for (MethodDeclaration tempMethod : ((TypeDeclaration) tempType).getMethods()) { // We only check the plain method name and omit the full // signature check here because fixture method names are // required to be unique per class if (aMethod.getSimpleName().equals(tempMethod.getName().getFullyQualifiedName())) { return tempMethod; } } } } return null; } /** * Returns a map of parameter names to Javadoc descriptions for a given Java method. This explores the @param * Javadoc parameter. Parameters which don't have such an information attached will not be put into the resulting * map. * * @param aMethod * the Java method to explore * @param anElementFinder * the element finder to use for locating the {@link IJavaElement} to the given method * @return the result map, or null if there is no readable Javadoc at all */ public static Map<String, String> getMethodParamJavadoc(JvmOperation aMethod, IJavaElementFinder anElementFinder) { MethodDeclaration tempMethodDeclaration = getMethodDeclaration(aMethod, anElementFinder); if (tempMethodDeclaration == null) { return null; } Javadoc tempJavaDoc = tempMethodDeclaration.getJavadoc(); if (tempJavaDoc != null) { Map<String, String> tempResultMap = new HashMap<String, String>(); for (Object tempTagObject : tempJavaDoc.tags()) { TagElement tempTag = (TagElement) tempTagObject; if (TagElement.TAG_PARAM.equals(tempTag.getTagName())) { if (tempTag.fragments().size() >= 2) { Object tempSimpleNameElement = tempTag.fragments().get(0); Object tempTextElement = tempTag.fragments().get(1); if ((tempSimpleNameElement instanceof SimpleName) && (tempTextElement instanceof TextElement)) { tempResultMap.put(((SimpleName) tempSimpleNameElement).getFullyQualifiedName(), ((TextElement) tempTextElement).getText()); } } } } return tempResultMap; } return null; } /** * Returns the Javadoc text attached to a given Java Method. * * @param aMethod * the method to explore * @param anElementFinder * the element finder to use for locating the {@link IJavaElement} for the given method * @return the Javadoc text, or null if none is available */ public static String getMethodJavadoc(JvmOperation aMethod, IJavaElementFinder anElementFinder) { MethodDeclaration tempMethodDeclaration = getMethodDeclaration(aMethod, anElementFinder); if (tempMethodDeclaration == null) { return null; } Javadoc tempJavaDoc = tempMethodDeclaration.getJavadoc(); if (tempJavaDoc != null) { return getJavadocMainText(tempJavaDoc); } return null; } /** * Returns the Javadoc description attached to a given {@link IField}. * * @param aField * the field to explore * @return the Javadoc String, or null if there is none */ public static String getFieldJavadoc(IField aField) { ICompilationUnit tempCompilationUnit = aField.getCompilationUnit(); AbstractTypeDeclaration tempType = parseCompilationUnit(tempCompilationUnit); if (tempType instanceof TypeDeclaration) { List<TypeDeclaration> tempTypes = new ArrayList<TypeDeclaration>(); tempTypes.add((TypeDeclaration) tempType); // also visit all subtypes (inner classes!) Collections.addAll(tempTypes, ((TypeDeclaration) tempType).getTypes()); for (TypeDeclaration tempTypeDeclaration : tempTypes) { for (FieldDeclaration tempField : tempTypeDeclaration.getFields()) { if (compareFields(tempField, aField)) { Javadoc tempJavadoc = tempField.getJavadoc(); if (tempJavadoc != null) { return getJavadocMainText(tempJavadoc); } else { break; } } } } } return null; } /** * Returns the Javadoc text attached to a given field. * * @param aField * the field * @param anElementFinder * the element finder * @return the Javadoc text or null if there is none */ public static String getJvmFieldJavadoc(JvmField aField, IJavaElementFinder anElementFinder) { IJavaElement tempElement = anElementFinder.findElementFor(aField); if (tempElement instanceof IField) { return getFieldJavadoc((IField) tempElement); } return null; } /** * Returns the main text part from the given Javadoc. * * @param aJavadoc * the Javadoc object to explore * @return the text, or null if there is none */ protected static String getJavadocMainText(Javadoc aJavadoc) { for (Object tempTagObject : aJavadoc.tags()) { TagElement tempTag = (TagElement) tempTagObject; if (tempTag.getTagName() == null) { StringBuffer tempText = new StringBuffer(); for (Object tempPossibleTextElement : tempTag.fragments()) { if (tempPossibleTextElement instanceof TextElement) { tempText.append(((TextElement) tempPossibleTextElement).getText()); tempText.append(' '); } } if (tempText.length() > 0) { return tempText.toString(); } } } return null; } /** * This checks whether a given {@link FieldDeclaration} and {@link IField} refer to the same field. * * @param aFieldDeclaration * the field declaration * @param aField * the field * @return true if both refer to the same field, false otherwise */ protected static boolean compareFields(FieldDeclaration aFieldDeclaration, IField aField) { @SuppressWarnings("unchecked") List<VariableDeclarationFragment> tempFragments = aFieldDeclaration.fragments(); for (VariableDeclarationFragment tempVariableDeclarationFragment : tempFragments) { if (tempVariableDeclarationFragment.getName().getIdentifier().equals(aField.getElementName())) { return true; } } return false; } /** * Returns the {@link AbstractTypeDeclaration} for the given {@link ICompilationUnit}. This basically parses the * given compilation unit into an Abstract Syntax Tree, using the parser provided by the JDT for the job. * * @param aCompilationUnit * the compilation unit to parse * @return the AST */ protected static AbstractTypeDeclaration parseCompilationUnit(ICompilationUnit aCompilationUnit) { ASTParser tempParser = ASTParser.newParser(AST.JLS4); tempParser.setSource(aCompilationUnit); tempParser.setIgnoreMethodBodies(true); tempParser.setKind(ASTParser.K_COMPILATION_UNIT); tempParser.setCompilerOptions(ASTPARSER_OPTIONS); CompilationUnit tempNode = (CompilationUnit) tempParser.createAST(null); return (AbstractTypeDeclaration) tempNode.types().get(0); } }